﻿using System.Security.Cryptography;
using System.Text;
using up6.utils;

namespace up6.store.minio;

/// <summary>
/// AWS签名算法V4
/// https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
/// CreateMultipartUpload
/// https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_CreateMultipartUpload.html
/// PutObject
/// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
/// </summary>
public class MinioAuthorization
{
    private MinioConfig cfg;
    private string timeCur = "";//20220519
    private string timeIso = "";
    private string method = "PUT";//PUT,GET,POST
    private string uri = "";//资源路径,/test/test.txt
    private string host = "";
    public string data_sha256_Hash = "";//内容-sha256   HexEncode(Hash(RequestPayload))
    public string dataMd5 = "";
    public int contentLength = 0;
    private DateTime timeNow = DateTime.UtcNow;
    private string contentType = "";
    private Dictionary<string, string> m_headers = new Dictionary<string, string>();
    private string CanonicalQueryString;//请求字符串

    public MinioAuthorization()
    {
        this.cfg = new MinioConfig();
        this.timeIso = this.timeNow.ToString("yyyyMMddTHHmmssZ");
        this.timeCur = this.timeNow.ToString("yyyyMMdd");

        //header
        this.m_headers.Add("content-length", "");
        this.m_headers.Add("content-type", "");
        this.m_headers.Add("host", "");
        this.m_headers.Add("content-md5", "");
        this.m_headers.Add("x-amz-content-sha256", "");
        this.m_headers.Add("x-amz-date", "");
    }

    public MinioAuthorization setConfig(MinioConfig v)
    {
        this.cfg = v;
        return this;
    }


    private String getHeaders()
    {
        string head = this.CanonicalHeaders();
        string[] arr = head.Split('\n');
        List<string> heads = new List<string>();

        foreach (string a in arr)
        {
            if (!string.IsNullOrEmpty(a))
            {
                int end = a.Length - 1;
                if (a.IndexOf(":") != -1) end = a.IndexOf(":");
                heads.Add(a.Substring(0, end));
            }
        }
        head = string.Join(";", heads.ToArray());
        return head;
    }


    public MinioAuthorization setMethod(String v)
    {
        this.method = v;
        return this;
    }

    public MinioAuthorization setData(byte[] v)
    {
        this.contentLength = v.Length;
        this.data_sha256_Hash = Md5Tool.base16(Md5Tool.sha256(v));
        //System.Diagnostics.Debug.WriteLine("内容-sha256-hash：\n" + this.data_sha256_Hash);
        this.dataMd5 = Md5Tool.calc(v);
        this.dataMd5 = this.dataMd5.Substring(8, 24);
        var arr = System.Text.Encoding.UTF8.GetBytes(this.dataMd5);
        this.dataMd5 = Convert.ToBase64String(arr);
        return this;
    }

    public String getDataHash() { return this.data_sha256_Hash; }
    public MinioAuthorization setUrl(String v)
    {
        Uri u = new Uri(v);
        this.host = u.Host;
        if (u.Port != 80)
        {
            this.host += ":";
            this.host += u.Port;
        }

        this.setUri(u.LocalPath);
        this.setQueryString(u.Query);
        return this;
    }
    public MinioAuthorization setUri(String v)
    {
        this.uri = PathTool.url_safe_encode(v);
        return this;
    }
    public MinioAuthorization setHost(String h)
    {
        this.host = h;
        return this;
    }
    public MinioAuthorization setContentType(String v)
    {
        this.contentType = v;
        return this;
    }
    public MinioAuthorization setQueryString(String v)
    {
        if (null == v) return this;
        if (string.IsNullOrEmpty(v)) return this;
        //?uploads -> uploads
        int pos = v.IndexOf("?");
        if (-1 != pos) v = v.Substring(pos + 1);

        string[] arr = v.Split('&');
        List<string> qs = new List<string>();
        foreach (string q in arr)
        {
            string a = q;
            //所有参数后面必须跟=
            if (q.IndexOf("=") == -1) a += "=";
            qs.Add(a);
        }

        this.CanonicalQueryString = string.Join("&", qs.ToArray());
        return this;
    }

    public MinioAuthorization delHead(String n)
    {
        this.m_headers.Remove(n.ToLower());
        return this;
    }

    public MinioAuthorization addHead(String n, String v)
    {
        this.m_headers.Add(n, v);
        return this;
    }

    public String getTimeIso()
    {
        return this.timeIso;
    }

    byte[] HmacSHA256(String data, byte[] key)
    {
        String algorithm = "HmacSHA256";
        KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
        kha.Key = key;

        return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
    }

    /**
     * 签名密钥 
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-calculate-signature.html
     * 	AWS4-HMAC-SHA256
        20150830T123600Z
        20150830/us-east-1/iam/aws4_request
        示例签名密钥
            f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59
     * @return
     * @throws Exception
     */
    byte[] getSignatureKey()
    {
        byte[]
        kSecret = System.Text.Encoding.UTF8.GetBytes("AWS4" + this.cfg.sk);
        byte[]
        kDate = HmacSHA256(this.timeCur, kSecret);
        byte[]
        kRegion = HmacSHA256(this.cfg.region, kDate);
        byte[]
        kService = HmacSHA256(this.cfg.service, kRegion);
        byte[]
        kSigning = HmacSHA256("aws4_request", kService);

        return kSigning;
    }

    /**
     * 规范标头
     * @return
     */
    string CanonicalHeaders()
    {
        string[] arr = {
                "content-length:"+this.contentLength,
				//"content-md5:"+this.dataMd5,
				"content-type:"+this.contentType,
                "host:"+this.host,
                "x-amz-content-sha256:"+this.data_sha256_Hash,
				//"x-amz-content-sha256:"+this.data_sha256_Hash,
				"x-amz-date:"+this.timeIso
        };

        //设置header值
        if (this.m_headers.ContainsKey("content-length"))
            this.m_headers["content-length"] = this.contentLength.ToString();
        if (this.m_headers.ContainsKey("content-type"))
            this.m_headers["content-type"] = this.contentType;
        if (this.m_headers.ContainsKey("host"))
            this.m_headers["host"] = this.host;
        if (this.m_headers.ContainsKey("content-md5"))
            this.m_headers["content-md5"] = this.dataMd5;
        if (this.m_headers.ContainsKey("x-amz-content-sha256"))
            this.m_headers["x-amz-content-sha256"] = this.data_sha256_Hash;
        if (this.m_headers.ContainsKey("x-amz-date"))
            this.m_headers["x-amz-date"] = this.timeIso;

        List<string> hs = new List<string>();
        foreach (KeyValuePair<string, string> k in this.m_headers)
        {
            hs.Add(k.Key + ":" + k.Value);
        }
        hs.Sort();

        string str = String.Join("\n", hs.ToArray()) + "\n";
        //Debug.WriteLine("规范标头："+str);
        return str;
    }

    /**
     * 凭证范围值，
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
     * 示例：
     * 	20150830/us-east-1/iam/aws4_request
     * @return
     */
    String CredentialScope()
    {
        string[] arr = {
                this.timeCur,
                this.cfg.region,
                "s3/aws4_request"
        };
        string str = String.Join("/", arr);
        //Debug.WriteLine("凭证范围：\n"+str);

        return str;
    }

    /// <summary>
    /// 
    /**
        * 规范请求头签名
        * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-canonical-request.html
        * 格式：
        * CanonicalRequest =
            HTTPRequestMethod + '\n' +
            CanonicalURI + '\n' +
            CanonicalQueryString + '\n' +
            CanonicalHeaders + '\n' +
            SignedHeaders + '\n' +
            HexEncode(Hash(RequestPayload))
        * @return
        */
    /// </summary>
    /// <returns></returns>
    string HashCanonicalRequest()
    {
        string[] arr = {
                this.method,
                this.uri,
                this.CanonicalQueryString,//请求字符串
				this.CanonicalHeaders(),//请求头参数
				this.getHeaders(),//签名头
				this.data_sha256_Hash//内容-sha256
		};

        string str = String.Join("\n", arr);
        //Debug.WriteLine("规范请求头：\n" + str);
        str = Md5Tool.sha256(str);
        //Debug.WriteLine("规范请求头签名：\n" + str);
        return str;
    }

    /**
     * 待签名字符串
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
     * 格式：
     * StringToSign =
        Algorithm + \n +
        RequestDateTime + \n +
        CredentialScope + \n +
        HashedCanonicalRequest
     * @return
     */
    private string StringSign()
    {
        string[] arr = {
                this.cfg.algorithm,
                this.timeIso,
                this.CredentialScope(),
                this.HashCanonicalRequest()
        };
        //待签名字符串
        string str = string.Join("\n", arr);

        //Debug.WriteLine("待签名字符串：\n" + str);
        //计算签名密钥
        byte[] key = this.getSignatureKey();
        //Debug.WriteLine("签名密钥：" + Md5Tool.base16(key));

        //签名
        str = Md5Tool.base16(this.HmacSHA256(str, key));
        //Debug.WriteLine("密钥编码：" + str);

        return str;
    }

    /**
     * 待签名字符串
     * @return
     */
    public string Credential()
    {
        string[] arr = {
                this.cfg.ak,//
				this.timeCur,//时间：20220519
				this.cfg.region,//region
				this.cfg.service,//服务
				"aws4_request"
        };
        string v = string.Join("/", arr);
        //System.out.println("待签名字符串："+v);
        return v;
    }

    /**
     * Authorization 标头,添加到Header中
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-add-signature-to-request.html
     * 格式：
     * 	Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
     * 示例：
     * 	Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
     * @return
     */
    public String Authorization()
    {
        return this.cfg.algorithm + " " +
                "Credential=" + this.Credential() + ", " +
                "SignedHeaders=" + this.getHeaders() + ", " +//签名头
                "Signature=" + this.StringSign();
    }
}